package philbot;

import java.util.ArrayList;
import java.util.List;

/**
 * The primary board representation for now
 * @author philip
 *
 */
public class CPosition extends AbstractPosition {

	private boolean[][] table;
	private int xLoc;
	private int yLoc;
	private States state;
	private boolean side;
	private boolean createdByJump;
	/**
	 * @see philbot.AbstractPosition#createdByJump()
	 */
	@Override 
	public boolean createdByJump(){
		return createdByJump;
	}
	
	private int xSize;
	private int ySize;
	private int[][] hashVals;
	
	/**
	 * The basic copy constructor.  Reverses the side, but makes no otehr changes.
	 */
	public CPosition(CPosition old) {
		this.xSize = old.xSize;
		this.ySize = old.ySize;
		this.hashVals = old.hashVals;
		this.table = new boolean[19][21];
		for(int i = 0; i<xSize;i++){
			for(int j = 0; j<ySize;j++){
				this.table[i][j] = old.table[i][j];
			}
		}
		this.xLoc = old.getxLoc();
		this.yLoc = old.getyLoc();
		this.side = !old.side;
		this.createdByJump = false;

	}
	/**
	 * The standard constructor
	 * @param table the board
	 * @param xLoc the x location of the ball
	 * @param yLoc the y location of the ball
	 * @param state the board "State"
	 * @param side the side to play
	 * @param hashVals an array used to compute the hash.  Must be consistent across multiple instances used by a memoizing engine
	 * @param xSize the x size of the board
	 * @param ySize the y size of the board
	 */
	public CPosition(boolean[][] table, int xLoc, int yLoc, States state, boolean side, int[][] hashVals, int xSize, int ySize){
		this.setTable(table);
		this.setxLoc(xLoc);
		this.setyLoc(yLoc+1);
		this.setState(state);
		this.side = side;
		this.xSize = xSize;
		this.ySize = ySize;
		this.hashVals = hashVals;
		this.createdByJump = false;
	}
	/**
	 * @return the side to move
	 */
	public boolean side(){
		return this.side;
	}


	/**
 	* @deprecated
 	*/
	public int eval(boolean side) {
		// TODO Build strong eval function
		// This should probably take into account position type
		int v = -1;
		if(this.side)
			v = 1;
		return v* this.eval();
	}
	/**
	 * @deprecated
	 */
	public int eval(){
		int v = -1;
		if(this.side)
			v = 1;
		return v* this.yLoc;
	}

	/**
	 * The evaluation method
	 * @see philbot.AbstractPosition#eval(philbot.Evaluator)
	 * @param a custom evaluator
	 * @return the desirability of this position
	 */
	@Override public int eval(Evaluator e){

		final boolean gs = this.side;
		//YAY FOR DOUBLE BRACKET INITIALIZATION
		Evaluator.Values v = new Evaluator.Values(){{
			lScore = yLoc;
			side = gs; //Shouldnt have used the same word "side"
		}};
		return e.eval(v);
	}
	
	/**
	 * @return canGet(false)
	 */
	public int canGet(){
		return canGet(false);
	}

	/**
	 * 
	 * @param side
	 * @return how far a simple set of forward jumps can get in that direction
	 */
	public int canGet(boolean side){
		/*
		 * We look for reachable positions using only forward jumps
		 * in some sense this is a dynamic programming problem, and is solved as such here
		 */
		//we tell which side we are on
		//this method makes extensive use of the ternary operator.
		//although generally not the best style, here it makes the code much simpler
		int pushv  = side ? 1 : -1;
		//a is all false
		boolean[][][] a = new boolean[xSize][ySize][4];//false http://download.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
		a[xLoc][yLoc][0] = true;
		int v = 0; //that zero is actually important
		//begin optimization:
		//this allows us to quickly 
		boolean[] potentials = new boolean[ySize];
		potentials[yLoc] = true;
		//end optimization
		//iterate through the rows
		/*
		for(int k = side ? ySize - 2 : 1; (!side) ? (k < ySize - 1) : (k > 1); k += pushv){
		*/
		for(int j = 1; j < ySize - 1; j++){
			int k = side ? (j) : (ySize - j);
			//next line is optimization:
			if(potentials[k]){
				//iterate through columns 
				for(int i = 0; i < xSize; i++){
					if(a[i][k][0]){
						if(i > 0){
							a = this.getHelper(a,i-1,k+pushv,1,false);
						}
						a = this.getHelper(a,i,k+pushv,2,false);
						if(i < xSize - 1){
							a = this.getHelper(a,i+1,k+pushv,3,false);
						}
						v = j+pushv;
						potentials[k+pushv] = true;
					}
					if(a[i][k][1] && (i > 0)){
						a = this.getHelper(a, i-1, k+pushv,1,true);
						v = j+2*pushv;
						potentials[k+pushv] = true;
					}
					if(a[i][k][2]){
						a = this.getHelper(a, i, k+pushv,2,true);
						v = j+2*pushv;
						potentials[k+pushv] = true;
					}
					if(a[i][k][3] && (i < xSize - 1)){
						a = this.getHelper(a, i+1, k+pushv,3,true);
						v = j+2*pushv;
						potentials[k+pushv] = true;
					}
				}
			}
		}
		if(side)
			v--;
		return v;
	}
	
	protected boolean[][][] getHelper(boolean[][][] a, int x, int y,
			int direc, boolean inair) {
				if(this.table[x][y]){
					a[x][y][direc] = true;
				}
				else{
					if(inair){
						a[x][y][0] = true;
					}
				}
				return a;
			}

	/**
	 * @see philbot.AbstractPosition#getChildren()
	 */
	@Override
	public List<AbstractPosition> getChildren() {
		//I had written a lazy inizalizer/memoization based version of this, but discovered that it prevented garbage collection
			//this stuff is throughly unsurprising if you read the "States" documentation
			List<AbstractPosition> rv = null;

			if(getState() == States.START){
				rv = this.getPuts();
				List<AbstractPosition> jumps = this.getJumps();
				for(AbstractPosition p: jumps){
					rv.add(p);
				}
			}else if(getState() == States.JUMP){
				rv = this.getJumps();
				CPosition p = new CPosition(this);
				p.setState(States.START);
				rv.add(p);
			}else{
				CPosition p = new CPosition(this);
				p.setState(States.JUMP);
				rv = new ArrayList<AbstractPosition>(1);
				rv.add(p);
			}
			return rv;
	}

	protected List<AbstractPosition> getPuts() {
		List<AbstractPosition> rv = new ArrayList<AbstractPosition>(xSize*(ySize - 2) + 1);
		for(int i = 0; i<xSize;i++){
			//can't put off the board
			for(int j = 1; j<ySize - 1;j++){
				if((!this.table[i][j])&&((this.xLoc != i)||(this.yLoc != j))){
					CPosition can = new CPosition(this);
					can.setTable(true, i, j);
					can.setState(States.START);
					rv.add(can);
				}
			}
		}
		return rv;
	}

	protected List<AbstractPosition> getJumps() {
		//8 is the maximal number of jumps produced
		List<AbstractPosition> rv = new ArrayList<AbstractPosition>(8);
		//for each possible direction
		for(int i = -1; i < 2; i++){
			for(int j = -1; j<2;j++){
				if((i!=0)||(j!=0)){
					CPosition can = this.jumpInDirec(i, j);
					if(can != null){
						rv.add(can);
					}
				}
			}
		}
		return rv;
	}

	/**
	 * Method to find the jump in a particular direction
	 * @param i a number between -1 and 1 representing the x direction to jump
	 * @param j a number between -1 and 1 representing the y direction to jump
	 * @return the position produced by the jump, or NULL if the jump is impossible
	 */
	protected CPosition jumpInDirec(int i, int j) {
		//note: this currently uses non short circuiting comparators since 
		//they tend to be somewhat faster for simple arithmetic on modern highly pipelined processors
		//this isn't tested and premature optimization is generally EVIL, but it is such a small change.
		//It does not impact correctness of boolean logic, but don't get scared by lack of ampersands
		/*
		//bounds checking.
		//skip the zero zero direction
	
		if((i > 1)|(i < -1)|(j > 1)|(j < -1)|((i==0)&(j==0)))
			return null;
		*/
		//these number represent the location we will try to jump to
		int x = this.getxLoc() + i;
		int y = this.getyLoc() + j;
		//is this position a place we can jump over?
		if((x < xSize)&(x > -1)&(y < ySize)&(y > -1)&&this.table[x][y]){
			//keep jumping until you find an empty spot
			do{
				x = x + i;
				y = y + j;
			}while((x < xSize)&(x > -1)&(y < ySize)&(y > -1)&&this.table[x][y]);
			//if that spot was valid
			if((x < xSize)&(x > -1)&(y < ySize)&(y > -1)){
				//we can build a new position to return
				//it will be based on this one:
				CPosition can = new CPosition(this);
				//we remove the pieces that are jumped over
				for(int k = 0;(k <= Math.max(Math.abs(x-xLoc),Math.abs(y-yLoc)));k++){
					can.setTable(false,xLoc + i*k,yLoc + j*k);
				}
				//set the remaining state
				can.setxLoc(x);
				can.setyLoc(y);
				can.setState(States.NULLMOVE);
				can.createdByJump = true;
				//return
				return can;
			}	
			return null;
		}else{
			return null;
		}
	}
	/**
 	* @see philbot.AbstractPosition#isVictory()
 	*/
	@Override
	public boolean isVictory() {
		// TODO Auto-generated method stub
		if((this.yLoc <= 1)|(this.yLoc >= this.ySize -2))
			return true;
		else
			return false;
	}
	/**
	 * @see philbot.AbstractPosition#whoVictory()
	 * 
	 */
	@Override public int whoVictory(){
		if(this.yLoc <= 1)
			return -1;
		if(this.yLoc >= this.ySize -2)
			return 1;
		return 0;
	}
	/**
	 * @param xLoc the xLoc to set
	 */

	protected void setxLoc(int xLoc) {
		this.xLoc = xLoc;
	}

	/**
	 * @return the xLoc
	 */
	protected int getxLoc() {
		return xLoc;
	}

	/**
	 * @param yLoc the yLoc to set
	 */
	protected void setyLoc(int yLoc) {
		this.yLoc = yLoc;
	}

	/**
	 * @return the yLoc
	 */
	protected int getyLoc() {
		return yLoc;
	}

	/**
	 * @param state the state to set
	 */
	protected void setState(States state) {
		this.state = state;
	}

	/**
	 * @return the state
	 */
	private States getState() {
		return state;
	}

	/**
	 * @param table the table to set
	 */
	protected void setTable(boolean[][] table) {
		this.table = table;
	}
	/**
	 * @param val the value to assign to the position in the table
	 * @param x the x position in the table
	 * @param y the y position in the table
	 */
	protected void setTable(boolean val, int x, int y) {
		this.table[x][y] = val;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuilder s = new StringBuilder();
		s.append("Side ").append(this.side).append(" in State ").append(this.state);
		s.append("\n").append("this position evaluates to: ").append(this.eval(true));
		s.append("and has a jump score of: ").append(this.canGet());
		s.append("\n score row!");
		for(int j = 1; j<ySize - 1; j++){
			s.append("\n");
			for(int i = 0; i<xSize; i++){
				if(this.table[i][j]){
					s.append("1");
				}else if((this.xLoc == i)&&(this.yLoc == j)){
					s.append("X");
				}else{
					s.append("0");
				}
				s.append(",");
			}
		}
		s.append("\n score row!");
		return s.toString();
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		//TODO Replace with fast hashing
		int goo = 0;
		for(int i = 0; i < xSize; i++){
			for(int j = 0; j<ySize-2; j++){
				if(this.table[i][j+1])
					goo = goo ^ this.hashVals[i][j];
			}
		}
		goo ^= this.state.hashCode();
		goo = (this.side) ? goo^Integer.MAX_VALUE : goo;
		return goo;
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object o) {
		if(o instanceof CPosition){
			CPosition n = (CPosition)o;
			if((this.side == n.side)&&(this.state.equals(n.state))&&(this.xLoc == n.xLoc)&&(this.yLoc == n.yLoc)){
				boolean cond = true;
				for(int i = 0; i<xSize;i++){
					for(int j = 1; j<ySize-1;j++){
						if(this.table[i][j] != n.table[i][j]){
							cond = false;
							break;
						}
					}
				}
				return cond;
			}
		}	
		return false;
	}

}